#ifndef GRIDTCPCONNECTION_H
#define GRIDTCPCONNECTION_H

#include "GridPackedMessage.h"
#include "BehaviorHandler.h"
#include <boost/asio.hpp>
#include <boost/cstdint.hpp>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <vector>
#include "Utils.h"
#include "Log.h"

/*!
 * \file GridTcpConnection.h
 * \author thomas raynal
 * \version 2.0
 */


namespace asio = boost::asio;
using asio::ip::tcp;
using boost::uint8_t;
/*! \namespace Network
 * 
 * Group client/server related classes. Offer API for handling both server and clients.
 */
namespace Network
{

/*! \class GridTcpConnection
* \brief Wrapper around boost TCP socket API 
* Messages model must be specified
*/
template <class MessageRequest, class MessageResponse>
class GridTcpConnection:
	public boost::enable_shared_from_this<GridTcpConnection<MessageRequest,MessageResponse>>
{
	public:

		static enum MESSAGE_TYPE {REQUEST,RESPONSE};

			/*!
			*  \brief Create a new connection (boost::shared pointer)
			*
			*/
		static boost::shared_ptr<GridTcpConnection<MessageRequest,MessageResponse>> Create(
			asio::io_service& io_service,
			BehaviorHandler<MessageRequest,MessageResponse>* handler
			)
		{
			return boost::shared_ptr<GridTcpConnection<MessageRequest,MessageResponse>>
				(new GridTcpConnection<MessageRequest,MessageResponse>(io_service,handler));
		}

		/*!
		*  \brief Getter for the connection socket
		*
		*/
		tcp::socket& GetSocket()
		{
			return clientSocket_;
		}

		/*!
		*  \brief Start the connection (socket switch to listen mode)
		*
		*/
		void Start()
		{
			StartReadHeader();
		}

		/*!
		*  \brief Close the connection (close the socket)
		*
		*/
		void Stop() 
		{
			clientSocket_.close();
		}


		/*!
		*  \brief Write a message on the socket
		*
		*/
		void Write(void* message,MESSAGE_TYPE type)
		{
			std::vector<boost::uint8_t> writebuf;

			switch (type)
			{
			case MESSAGE_TYPE::REQUEST:
				{
					MessageRequest * request= static_cast<MessageRequest*>(message);
					boost::shared_ptr<MessageRequest> ptr(request);
					GridPackedMessage<MessageRequest> request_msg(ptr);
					request_msg.set_type(TAG_REQUEST);
					request_msg.pack(writebuf);
					break;
				}
			case MESSAGE_TYPE::RESPONSE:
				{
					MessageResponse * response= static_cast<MessageResponse*>(message);
					boost::shared_ptr<MessageResponse> ptr(response);
					GridPackedMessage<MessageResponse> response_msg(ptr);
					response_msg.set_type(TAG_RESPONSE);
					response_msg.pack(writebuf);
					break;
				}
			}
			try {

			boost::asio::write(clientSocket_, boost::asio::buffer(writebuf));
			}
			catch (std::exception const& e) {
				std::cout<<e.what()<<std::endl;
			}
		}

		GridTcpConnection(asio::io_service& io_service, BehaviorHandler<MessageRequest,MessageResponse>* h)
		:clientSocket_(io_service),
		requestMessage_(boost::shared_ptr<MessageRequest>(new MessageRequest())),
		responseMessage_(boost::shared_ptr<MessageResponse>(new MessageResponse()))
		{
				active_ = true;
				task_sent=false;
				handler_ = h;
				requestMessage_.set_type(TAG_REQUEST);
				responseMessage_.set_type(TAG_RESPONSE);
				name_ = new std::string(GetRandomString());
		}

		/*!
		*  \brief Is a task being proceed by connected client?
		*
		*/
		bool IsTaskSent()
		{
			return task_sent;
		}

		/*!
		*  \brief Tag the connection as being busy.
		*
		*/
		void SendTaskSent(bool sent)
		{
			task_sent=sent;
		}

		/*!
		*  \brief Check if the connection is alive (try to write on the socket).
		*
		*/
		void CheckIsAlive()
		{
			if (!clientSocket_.is_open())
			{
				HandleTestIsAlive();
			}
		}

		/*!
		*  \brief Is the connection alive
		*
		*/
		bool IsActive()
		{
			return active_;
		}

		/*!
		*  \brief Getter on the connection name.
		*
		*/
		std::string* GetName()
		{
			return name_;
		}

		bool operator==(const GridTcpConnection< MessageRequest,MessageResponse>& connection)
		{
			bool result = false;
			if (*name_==*connection.GetName())
			{
				result = true;
			}

			return result;
		}

private:
		BehaviorHandler<MessageRequest,MessageResponse>* handler_;
		bool active_;
		bool task_sent;
		std::string* name_;
		tcp::socket clientSocket_;
		vector<uint8_t> buffer_check_alive;
		vector<uint8_t> buffer_;
		GridPackedMessage<MessageRequest> requestMessage_;
		GridPackedMessage<MessageResponse> responseMessage_;
	
		
		void StartReadHeader()
		{
				buffer_.resize(HEADER_SIZE);
				boost::asio::async_read(clientSocket_, boost::asio::buffer(buffer_),
				boost::asio::transfer_at_least(1),
				boost::bind(&GridTcpConnection::HandleReadHeader, shared_from_this(),
				boost::asio::placeholders::error)
				);
		}

		void HandleReadHeader(const boost::system::error_code& error)
		{
			if (!error)
			{
				 int msgLen = GridPackedMessage<int>::decode_header(buffer_);
				 StartReadBody(msgLen);
			}
			else 
			{
				PrintLog("Connection closed");
				active_= false;
			}
		}


		void StartReadBody (unsigned size)
		{
			buffer_.resize(HEADER_SIZE + size);
			boost::asio::async_read(clientSocket_, boost::asio::buffer(&buffer_[HEADER_SIZE],size),
			boost::asio::transfer_at_least(1),
			boost::bind(&GridTcpConnection::HandleReadBody, shared_from_this(),
			boost::asio::placeholders::error)
			);
		}

		void HandleReadBody(const boost::system::error_code& error)
		{
			if (!error)
			{
					switch (static_cast<unsigned>(buffer_[4]))
					{
					case TAG_REQUEST:
						HandleResponse();
						break;

					case TAG_RESPONSE:
						HandleRequest();
						break;
					}

				StartReadHeader();
			}

		}

		void HandleResponse ()
		 {
			 if (requestMessage_.unpack(buffer_)) {
				 MessageResponse* response = new MessageResponse();
				 handler_->HandleRequest(*response,*requestMessage_.get_msg().get(),NULL);
				 if (handler_->GetKillSwitch()==false)
				 {
				 Write(response,MESSAGE_TYPE::RESPONSE);
				 }
			 }
		 }

		void HandleRequest ()
		 {
			if (responseMessage_.unpack(buffer_)) {
				 MessageRequest* request = new MessageRequest();
				 handler_->HandleResponse(*responseMessage_.get_msg().get(),*request, NULL);
				 Write(request,MESSAGE_TYPE::REQUEST);
			 }
		 }
		 
    void HandleTestIsAlive()
		{
				active_ = false;
				PrintLog("Connection closed");
		}


};
}
#endif GRIDTCPCONNECTION_H

